#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <chrono>
#include <sys/time.h>

#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/opencv.hpp"
#include "cJSON/cJSON.h"

#include "rkmedia_api.h"
#include "sample_common.h"
#include "rockx.h"


#define __STDC_CONSTANT_MACROS
extern "C"
{
    #include <libavutil/avassert.h>
    #include <libavutil/channel_layout.h>
    #include <libavutil/opt.h>
    #include <libavutil/mathematics.h>
    #include <libavutil/timestamp.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
    #include <libswresample/swresample.h>
    #include <libavcodec/avcodec.h>
    #include <libavfilter/buffersink.h>
    #include <libavfilter/buffersrc.h>
    #include "libavutil/time.h"
}

#include <mqueue.h>
#include <sys/msg.h>

#define CAMERA_PATH "rkispp_scale0"
#define CAMERA_ID 0
#define CAMERA_CHN 0
#define VENC_CHN 0
// #define VDEC_CHN 1
#define VDEC_CHN 0
#define MUXER_CHN 0

//长宽需是16的整数倍
#define WIDTH 640
#define HEIGHT 480

#define MEMORY_SIZE 256


#define MSG_NAME "/msg_file_vdec"
#define OUTPUT_URL "/demo/bin/boa/output.ts"
#define OUTPUT_MP4 "/demo/bin/boa/output.mp4"

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

AVStream *video_st;
AVFormatContext *ofmt_ctx;
AVFormatContext *m_pInFmtCtx = NULL;
AVPacket *packet;

void *sharedMemory_Video;
void *sharedMemory_Param;

int semid;
struct sembuf sem_p;
struct sembuf sem_v;

int msgid;

int start_record = 0;
int start_remoting_monitor = 0;

// rockx_handle_t face_det_handle;

typedef struct
{
    long mtype;
    int data;
} Message;
Message message;

typedef struct Camera_Param
{
    unsigned int width;
    unsigned int height;
    char url_protocol[32];
    char url_name[16];
    char codec_type[16];
} Camera_Param;
Camera_Param *camera_param;

pthread_mutex_t ffmpeg_lock = PTHREAD_MUTEX_INITIALIZER;

AVPacket filtered_packet;
const AVBitStreamFilter *bsfc;
AVBSFContext *bsf;
AVBitStreamFilterContext *h264bsfc;
AVRational framerate;

AVOutputFormat *ofmt = NULL;
// 创建输入AVFormatContext对象和输出AVFormatContext对象
AVFormatContext *ifmt_ctx = NULL;

AVFormatContext *output_format_context = NULL;
AVStream *video_stream = NULL;
AVDictionary *opts = NULL;

// AVPacket pkt;
int stream_index = 0;
int *stream_mapping = NULL;
int stream_mapping_size = 0;

static RK_U32 gu32FileIdx;

void handle_quit_signal(int sig)
{
    printf("vi_program quit....\n");
}

void nv12ToBgr(uint8_t *data, int width, int height, uint8_t *bgr)
{
    cv::Mat yuvMat(height + height / 2, width, CV_8UC1, (uchar *)data); // YUV的Mat矩阵
    cv::Mat bgrMat(height, width, CV_8UC3, bgr);                     // bgr的Mat矩阵
    cv::cvtColor(yuvMat, bgrMat, cv::COLOR_YUV2RGB_YV12);
}

void *read_msg_thread(void *args)
{
    size_t size;
    struct msgbuf pmsgbuf;
    char filename[60];

    unsigned char *bgr = (unsigned char *)malloc(WIDTH * HEIGHT * 3);

    message.mtype = 0;
    message.data = 0;
    while (1)
    {
        size = msgrcv(msgid, &message, sizeof(int), 1, 0); //不断的接收消息
        // message.data = 1;
        // size = 1;
        if (size > 0)
        {
            printf("id: %d receiced: %d\n", msgid, message.data);
            if (message.data == 1) //snap_rv1126_vdec_picture.cpp 抓拍
            {
                printf("Snap Start...\n");
                srand(time(NULL)); // 生成随机数
                int random_number = rand();
                
                cv::Mat tmp_img = cv::Mat(HEIGHT, WIDTH, CV_8UC1, sharedMemory_Video); // 把RV1126的VI数据转换成Mat矩阵
                nv12ToBgr(tmp_img.data, WIDTH, HEIGHT, bgr); // NV12转换成BGR
                cv::Mat bgrMat(HEIGHT, WIDTH, CV_8UC3, bgr);                  

                time_t now;
                struct tm *timeinfo;
                char time_str[80];
                timeinfo = localtime(&now);
                strftime(time_str, sizeof(time_str), "%Y%m%d%H%M%S", timeinfo);

                // sprintf(filename, "/demo/bin/boa/www/Image/vdec_%d.jpg", rand()); // sprintf修改名称，名称前缀是vdec_xxx.jpg,xxx是随机生成的数字
                sprintf(filename, "/demo/bin/boa/www/Image/vdec_%s.jpg", time_str); 
                imwrite(filename, bgrMat);
            }

            if (message.data == 2)//start_record_cgi.cpp 开始录制消息
            {
                start_record = 1;
            }

            if (message.data == 3) //stop_record_cgi.cpp 停止录制消息
            {
                start_record = 0;
            }

            if (message.data == 4) //save_mp4_config.cpp save_ffmpeg_config.cpp 发送的消息
            {   
                //重新运行rv1126_vdec_stream程序
                if (execl("/demo/bin/boa", "rv1126_vdec_stream", NULL, (char *)NULL) == -1)
                {
                    printf("execl success...\n");
                }
                else
                {
                    printf("execl failed...\n");
                }
            }
        }
    }
}

void *read_ffmpeg_thread(void *args)
{
    pthread_detach(pthread_self());
    MB_IMAGE_INFO_S stImageInfo = {WIDTH, HEIGHT, WIDTH, HEIGHT, IMAGE_TYPE_YUV420P};
    int ret;
    int frame_index = 0;

    while (1)
    {
        if (av_read_frame(m_pInFmtCtx, packet) >= 0) //ffmpeg抽帧
        {   
            MEDIA_BUFFER mb = RK_MPI_MB_CreateImageBuffer(&stImageInfo, RK_TRUE, MB_FLAG_NOCACHED);
            memcpy(RK_MPI_MB_GetPtr(mb), packet->data, packet->size);
            RK_MPI_MB_SetSize(mb, packet->size);           
#if 1
            if (start_record == 1)
            {
                packet->stream_index = 0;
                AVRational time_base1 = m_pInFmtCtx->streams[0]->time_base;
                //源帧率的倒数
                int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(m_pInFmtCtx->streams[0]->r_frame_rate); 
                //PTS
                packet->pts = (double)(frame_index * calc_duration) / (double)(av_q2d(time_base1) * AV_TIME_BASE);
                //DTS
                packet->dts = packet->pts;
                //视频的总长度
                packet->duration = (double)calc_duration / (double)(av_q2d(time_base1) * AV_TIME_BASE);
                frame_index++;

                //时间基的转换:H264的时间基转换成MP4时间基
                av_packet_rescale_ts(packet, m_pInFmtCtx->streams[0]->time_base, output_format_context->streams[0]->time_base);
                // 把每一帧AVPACKET写到MP4文件里面
                ret = av_write_frame(output_format_context, packet);
                if (ret < 0)
                {
                    fprintf(stderr, "Error muxing packet\n");
                    break;
                }
                av_packet_unref(packet);
            }
#endif
            printf("read_ffmpeg_thread size=%d\n",RK_MPI_MB_GetSize(mb));
            RK_MPI_SYS_SendMediaBuffer(RK_ID_VDEC, VDEC_CHN, mb); //发送到VDEC解码器（模块号，通道号，编码数据流）
            RK_MPI_MB_ReleaseBuffer(mb);
        }
    }

    return NULL;
}

double __get_us(struct timeval t) { return (t.tv_sec * 1000000 + t.tv_usec); }

void *rv1126_vdec_thread(void *args)
{
    pthread_detach(pthread_self());
    MEDIA_BUFFER mb;
    char buffer[50];

/****目标跟踪******************************************************************************************************* */
    rockx_ret_t ret;
    rockx_handle_t object_det_handle;
    rockx_handle_t object_track_handle;

    char *rockx_data = "/demo/src/rockx_data";
    rockx_config_t *config = rockx_create_config();
    rockx_add_config(config, ROCKX_CONFIG_DATA_PATH, rockx_data);

    ret = rockx_create(&object_det_handle, ROCKX_MODULE_OBJECT_DETECTION, config,sizeof(rockx_config_t));
    if (ret != ROCKX_RET_SUCCESS) 
    {
        printf("ERROR: init rockx module ROCKX_MODULE_OBJECT_DETECTION error %d\n", ret);
    }

    ret = rockx_create(&object_track_handle, ROCKX_MODULE_OBJECT_TRACK, config,sizeof(rockx_config_t));
    if (ret != ROCKX_RET_SUCCESS)
    {
        printf("ERROR: init rockx module ROCKX_MODULE_OBJECT_DETECTION error %d\n", ret);
    }
    struct timeval start_time, stop_time;
/********************************************************************************************************** */
    while (1)
    {
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VDEC, VDEC_CHN, -1); //从VDEC中获取解码数据，-1表示阻塞的方式获取VDEC码流
        if (!mb)
        {
            printf("Get vdec Mb Break.....\n");
            continue;
        }
        printf("rv1126_vdec_thread size=%d\n",RK_MPI_MB_GetSize(mb));
        if(RK_MPI_MB_GetSize(mb)==0)
            continue;

        cv::Mat sharememory_mat = cv::Mat(HEIGHT, WIDTH, CV_8UC1, RK_MPI_MB_GetPtr(mb));
/*******目标跟踪************************************************************************************************** */
        gettimeofday(&start_time, NULL);
        rockx_image_t input_image;
        rkMB_IMAGE_INFO ImageInfo={0};
        RK_MPI_MB_GetImageInfo(mb,&ImageInfo);
        input_image.width=ImageInfo.u32Width;   
        input_image.height=ImageInfo.u32Height;
        input_image.pixel_format = ROCKX_PIXEL_FORMAT_YUV420SP_NV12;
        input_image.size = RK_MPI_MB_GetSize(mb);
        input_image.data = (uint8_t *)RK_MPI_MB_GetPtr(mb);

        rockx_object_array_t object_array;
        memset(&object_array, 0, sizeof(rockx_object_array_t));
        ret = rockx_object_detect(object_det_handle, &input_image, &object_array, nullptr);
        if (ret != ROCKX_RET_SUCCESS) 
        {
            printf("ERROR: rockx_object_detect error %d\n", ret);
        }

        int max_track_time = 4;
        rockx_object_array_t out_track_objects;
        ret = rockx_object_track(object_track_handle, input_image.width,  input_image.height, 
                                max_track_time,&object_array, &out_track_objects);
        if (ret != ROCKX_RET_SUCCESS) 
        {
            printf("ERROR: rockx_object_track error %d\n", ret);
        }

        for (int i = 0; i < out_track_objects.count; i++) 
        {
            int left = out_track_objects.object[i].box.left;
            int top = out_track_objects.object[i].box.top;
            int right = out_track_objects.object[i].box.right;
            int bottom = out_track_objects.object[i].box.bottom;
            int cls_idx = out_track_objects.object[i].cls_idx;
            const char *cls_name = ROCKX_OBJECT_DETECTION_LABELS_91[cls_idx];
            float score = out_track_objects.object[i].score;
            int track_id = out_track_objects.object[i].id;
            printf("\nbox=(%d %d %d %d) cls_name=%s, score=%f track_id=%d\n", 
                    left, top, right, bottom,cls_name, score, track_id);

            char show_str[32];
            memset(show_str, 0, 32);
            snprintf(show_str, 32, "%d-%s", track_id, cls_name);

            // 采用opencv来绘制矩形框,颜色格式是B、G、R
            cv::rectangle(sharememory_mat,cv::Point(left, top),cv::Point(right, bottom),cv::Scalar(255, 0, 255),3);
            std::string text=show_str;
            // 采用opencv在框的旁边绘制文字,颜色格式是B、G、R
            cv::putText(sharememory_mat, text, cv::Point(left, top-16), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 1);
        }
        gettimeofday(&stop_time, NULL);
        printf("once run use %f ms\n",(__get_us(stop_time) - __get_us(start_time)) / 1000);
/********************************************************************************************************** */
        //加时间戳
        auto now = std::chrono::system_clock::now();
        std::time_t now_time = std::chrono::system_clock::to_time_t(now);
        std::tm *ptm = std::localtime(&now_time);

        // 使用std::put_time进行格式化
        std::ostringstream oss;
        oss << std::put_time(ptm, "%Y-%m-%d %H:%M:%S");
        std::string time_str = oss.str();
        
        cv::putText(sharememory_mat, time_str, cv::Point(10, 50), 
        cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 1);

        semop(semid, &sem_p, 1); //拷贝数据之前，表示的是进入临界区
        memcpy(sharedMemory_Video, RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb));
        semop(semid, &sem_v, 1); //离开临界区，下一个数据就可以进行写入了

        // printf("Get VDEC One Frame.......\n");
        RK_MPI_MB_ReleaseBuffer(mb);
    }

    return NULL;
}

int main(int argc, char *argv[])
{
#if 1
    //读取json文件并解析url地址
    FILE *file = fopen("/demo/bin/boa/www/cjson_dir/network_detail.json", "r");
    if (file == NULL)
    {
        perror("打开文件时出错");
        return EXIT_FAILURE;
    }
    //获取文件大小
    fseek(file, 0, SEEK_END);
    long length = ftell(file);

    fseek(file, 0, SEEK_SET);
    char *data = (char *)malloc(length + 1);
    fread(data, 1, length, file);
    data[length] = '\0';

    cJSON *json = cJSON_Parse(data);
    if (json == NULL)
    {
        printf("json parse error....\n");
        return 1;
    }
    
    cJSON *url_address_json = cJSON_GetObjectItem(json, "url_address");
    if (url_address_json == NULL)
    {
        printf("get url_address error....\n");
        return 1;
    }
    char *network_address = url_address_json->valuestring;

    cJSON *record_file_json = cJSON_GetObjectItem(json, "record_file");
    if (url_address_json == NULL)
    {
        printf("get record_file error....\n");
        return 1;
    }    
    char *record_file = record_file_json->valuestring;
#else
    const char* network_address="udp://192.168.2.100:8080";
    // const char* network_address="/demo/bin/bigbuckbunny_1280x720_24fps_annexb_012.h264";
    const char *record_file = "record.mp4";
#endif

    key_t key_msg;
    key_msg = ftok(MSG_NAME, 'k');

    msgid = msgget(key_msg, 0655 | IPC_CREAT);
    printf("msgid = %d\n", msgid);
    
    //创建解码器
    int ret;
    RK_MPI_SYS_Init();
    VDEC_CHN_ATTR_S stVdecAttr;
    stVdecAttr.enCodecType = RK_CODEC_TYPE_H264;

    //不支持软件解码
    // stVdecAttr.enMode = VIDEO_MODE_FRAME;
    // stVdecAttr.enDecodecMode = VIDEO_DECODEC_SOFTWARE;
    
    //硬件解码
    stVdecAttr.enMode = VIDEO_MODE_STREAM;
    stVdecAttr.enDecodecMode = VIDEO_DECODEC_HADRWARE;

    ret = RK_MPI_VDEC_CreateChn(VDEC_CHN, &stVdecAttr);
    if (ret)
    {
        printf("Create Vdec[0] failed! ret=%d\n", ret);
        return -1;
    }
    else
    {
        printf("Create Vdec[0] Success!\n");
    }

#if 1
    //视频数据信号量的初始化
    key_t sem_key = ftok("/etc/semfile", 65);     // 生成IPC键值
    semid = semget(sem_key, 1, 0666 | IPC_CREAT); // 创建信号量集合，只有一个信号量
    union semun sem_union;
    sem_union.val = 1;                   // 初始化信号量为1
    semctl(semid, 0, SETVAL, sem_union); // 设置信号量的默认值

    sem_p = {0, -1, 0}; //P操作表示的是进入临界点, 一般是写入数据之前
    sem_v = {0, 1, 0}; //V操作指的是走出临界点，一般是写入数据之后
#endif

    const char *pathname = "/etc/video_share_vdec";
    int proj_id = 65;
    key_t key = ftok(pathname, proj_id);

    int shmid = shmget(key, WIDTH * HEIGHT * 3 / 2, IPC_CREAT | 0644); // 创建解码VDEC视频数据共享内存段
    if (shmid == -1)
    {
        printf("Failed to create shared memory...\n");
    }
    else
    {
        printf("Success Create Shared Memory...\n");
    }
    sharedMemory_Video = shmat(shmid, 0, 0); //对共享内存进行映射

    //共享摄像头参数内存 w,h,url
    const char *pathname_param = "/etc/video_share_param";
    int proj_param_id = 65;
    key_t key_param = ftok(pathname_param, proj_param_id);

    int shmid_param = shmget(key_param, MEMORY_SIZE, IPC_CREAT | 0644); // 创建共享内存段
    if (shmid_param == -1)
    {
        printf("Failed to create Shared_param_memory...\n");
    }
    else
    {
        printf("Success Create Shared_Param Memory...\n");
    }
    // sharedMemory_Param = shmat(shmid_param, 0, 0);
    camera_param = (Camera_Param *)shmat(shmid_param, 0, 0);

    int nRet;
    // ffmpe拉流
    avformat_network_init(); //初始化网络配置
    //if (avformat_open_input(&m_pInFmtCtx, "udp://192.168.100.66:8080", 0, 0) < 0)
    if (avformat_open_input(&m_pInFmtCtx, network_address, 0, 0) < 0) //初始化FFMPEG拉流输入模块
    {
        printf("failed to open input file\n");
    }
    printf("==> avformat_open_input!\n");

    // 可以使用avformat_find_stream_info()获取更多的码流信息。比如获取视频帧率、视频宽高，重新计算最大分析时长，打开解码器解码获取codec数据
    avformat_find_stream_info(m_pInFmtCtx, nullptr);
    printf("===========Input Information==========\n");

    // 通过av_find_best_stream()函数来获取流的索引
    int nVideo_indx = av_find_best_stream(m_pInFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (nVideo_indx < 0)
    {
        avformat_free_context(m_pInFmtCtx);
    }
    printf("==> av_find_best_stream!\n");
    packet = av_packet_alloc();

    framerate = m_pInFmtCtx->streams[0]->avg_frame_rate;

    //摄像头参数拷贝到共享内存
    // char buffer[MEMORY_SIZE];
    // sprintf(buffer, "width:%d,height:%d,url:%s", m_pInFmtCtx->streams[0]->codecpar->width, m_pInFmtCtx->streams[0]->codecpar->height, network_address);
    // memcpy(sharedMemory_Param, buffer, MEMORY_SIZE);
    camera_param->width = m_pInFmtCtx->streams[0]->codecpar->width;
    camera_param->height = m_pInFmtCtx->streams[0]->codecpar->height;
    int codec_id = m_pInFmtCtx->streams[0]->codecpar->codec_id;
    if (codec_id == AV_CODEC_ID_H264)
    {
        strcpy(camera_param->codec_type, "H264");
    }
    else if (codec_id == AV_CODEC_ID_H265)
    {
        strcpy(camera_param->codec_type, "H265");
    }
    else if (codec_id == AV_CODEC_ID_MJPEG)
    {
        strcpy(camera_param->codec_type, "MJPEG");
    }
    strcpy(camera_param->url_protocol, network_address);

    char output_file[128];
    sprintf(output_file, "/demo/bin/boa/www/Video/%s", record_file);
    //录制视频步骤
    //1.初始化输出的AVFormatContext结构体指针
    avformat_alloc_output_context2(&output_format_context, NULL, NULL, output_file); 

    printf("hhhhhhhhhhhhhhhhhhhhhhhhh\n");
    //2.初始化AVSstream结构体
    video_stream = avformat_new_stream(output_format_context, NULL);
    if (video_stream == NULL)
    {
        fprintf(stderr, "Failed to create video stream\n");
    }

    printf("kkkkkkkkkkkkkkkkkkkkkkk\n");

    //avcodec_parameters_copy(video_stream->codecpar, m_pInFmtCtx->streams[0]->codecpar);
    //3.把输入的AVFormatContext拷贝到输出的AVFormatContext结构体
    avcodec_parameters_copy(video_stream->codecpar, m_pInFmtCtx->streams[nVideo_indx]->codecpar);
    av_dump_format(output_format_context, 0, OUTPUT_MP4, 1);

    printf("uuuuuuuuuuuuuuuuuuuuuuuuuu\n");
    //4.初始化AVIOContext结构体
    avio_open(&output_format_context->pb, output_file, AVIO_FLAG_WRITE);
    //5.对AVDictionary设置MP4关键标识符{movflags:...} 
    //frag_keyframe在视频关键帧进行切片、empty_moov初始化空的moov box、 default_base_moof在tfhd容器中设置default-base-is-moof
    av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0);
    //6.写入文件头部同时加上AVDictionary选项
    ret = avformat_write_header(output_format_context, &opts);
    if (ret < 0)
    {
        printf("avformat_write_header failed!");
    }

    pthread_t pid;
    //7.录制MP4文件线程
    pthread_create(&pid, NULL, read_ffmpeg_thread, NULL);
    //解码线程
    pthread_create(&pid, NULL, rv1126_vdec_thread, NULL);
    //读取信号量
    pthread_create(&pid, NULL, read_msg_thread, NULL); 

    while(1){};

    RK_MPI_VDEC_DestroyChn(VDEC_CHN);

    return 0;
}
